#ifndef _Infrastructure_BufferQueue_h_
#define _Infrastructure_BufferQueue_h_

#include <Infrastructure/Container/FixedSizeQueue.h>

#include <boost/thread/mutex.hpp>
#include <boost/thread/condition.hpp>

#include <limits>

namespace Infra
{

/*
Buffer Queue is a series of variable size buffers.
It is not a ring buffer. It is analogous to a train
the moves forward by removing old tracks and placing
them in the front. We will create new variable sized tracks 
as needed and store a collection of old tracks.

As opposed to the ring buffer, the queue has no fixed
limit in size and all push/pops are guaranteed to be contiguous.

*/

class BufferQueue
{
public:
// Basic types
	struct Node;

// This is the structure that is returned from a pop
	template < typename UserType = void >
	struct Out
	{
		Out( void )
		: len( 0 ), userData( 0 ), data( 0 ) {}

		template < typename ParamType >
		Out( const Out< ParamType >& rhs )
		: len( rhs.len )
		, userData( reinterpret_cast< const UserType* >( rhs.userData ) )
		, data( rhs.data ) {}

		// Length of data
		size_t len;
		// A pointer to user data
		const UserType* userData;
		// The pointer to the raw data
		const char* data;
	};

// Use stats to check performance
	struct Stats
	{
		int push;
		int pop;
		int pushAboveChunk;
		int allocNode;
		int reuseNode;
		int discardNode;
		int emptied;
		int liveSize;
	};

// Construction
	BufferQueue( const size_t chunkSize = 1024, const size_t limit = std::numeric_limits<size_t>::max() );

	~BufferQueue( void );

// Accessors
	const Stats& getStats( void ) const
	{
		boost::mutex::scoped_lock lock( m_guard );
		return m_stats;
	}

	bool empty( void ) const
	{
		boost::mutex::scoped_lock lock( m_guard );
		return ( m_root == 0 );
	}

// Manipulators
	// push adds the specified raw data and the optional user data
	template < typename UserData >
	bool push( const char* data, size_t len, const UserData& userData = UserData() )
	{
		boost::mutex::scoped_lock lock( m_guard );
		return push( data, len, sizeof( UserData ), &userData, doSet< UserData > );
	}

	// removes the earliest raw data and the optional user data
	template < typename UserData >
	class Pop
	{
	public:
		Pop( BufferQueue& queue )
		: m_queue( queue )
		{
			boost::mutex::scoped_lock lock( m_queue.m_guard );
			assert( m_queue.m_busyNode == 0 && "Can only have one peek" );
			m_out = m_queue.pop( sizeof( UserData ) );
			m_queue.m_busyNode = m_queue.m_readNode;
		}

		~Pop( void )
		{
			boost::mutex::scoped_lock lock( m_queue.m_guard );
			m_queue.m_busyNode = 0;
			m_queue.m_readCondition.notify_all();
		}

		operator const Out< UserData >& ( void ) const
		{
			return m_out;
		}

	private:
		BufferQueue& m_queue;
		Out< UserData > m_out;
	};

	// looks at the earliest raw data and the optional user data
	template < typename UserData >
	class Peek
	{
	public:
		Peek( BufferQueue& queue )
		: m_queue( queue )
		{
			boost::mutex::scoped_lock lock( m_queue.m_guard );
			assert( m_queue.m_busyNode == 0 && "Can only have one peek" );
			std::pair< Out<>, Node* > result = m_queue.peek( sizeof( UserData ) );
			m_out = result.first;
			m_queue.m_busyNode = result.second;
		}

		~Peek( void )
		{
			boost::mutex::scoped_lock lock( m_queue.m_guard );
			m_queue.m_busyNode = 0;
			m_queue.m_readCondition.notify_all();
		}

		operator const Out< UserData >& ( void ) const
		{
			return m_out;
		}

		Out< UserData >& popIt( void )
		{
			boost::mutex::scoped_lock lock( m_queue.m_guard );
			m_queue.m_busyNode = 0;
			m_queue.m_readCondition.notify_all();
			m_out = m_queue.pop( sizeof( UserData ) );
			return m_out;
		}

	private:
		BufferQueue& m_queue;
		Out< UserData > m_out;
	};

	// Do not call clear within the scope of a Pop or Peek
	void clear( void )
	{
		boost::mutex::scoped_lock lock( m_guard );
		internalClear();
	}

private:
	friend class Pop;
	friend class Peek;

	void internalClear( void );

	// Worker method type for user data push
	typedef void ( *Setter )( void* dest, const void* src );

	// Does the work of pushing data into the queue
	bool push( const char* data, size_t len, size_t userDataLen, const void* userData, Setter setter );
	// Worker method for user data push
	template < typename UserData >
	static void doSet( void* dest, const void* src ) { *(UserData*)dest = *(UserData*)src; }

	// Does the work of popping data from the queue
	Out<> pop( size_t userDataLen );
	// Worker method for moving to the next allocated chunk
	void popSkip( void );

	std::pair< Out<>, Node* > peek( size_t userDataLen );

	// The following maintains the nodes in the queue
	Node* getNode( size_t size );
	void junkNode( Node* node );
	void deleteNode( Node* node );

	// The default/min chunk size
	const size_t m_chunkSize;
	const size_t m_sizeLimit;

	// Push/Pop guard
	mutable boost::mutex m_guard;
	boost::condition m_readCondition;

	// Storage for the reusable buffers
	FixedSizeQueue< Node*, 10 > m_spares;
	
	// The root node
	Node* m_root;

	// Where the next write (push) will happen
	Node* m_writeNode;
	size_t m_writeOffset;

	// Where the next read (pop) will happen
	Node* m_readNode;
	size_t m_readOffset;
	Node* m_busyNode;

	// Record what happens
	Stats m_stats;
};

}

#endif
